package com.lagou.oauth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.sql.DataSource;

/**
 * @Author:丁成煜 当前类为Oauth2 server的配置类（需要继承特定的父类  AuthorizationServerConfigurerAdapter）
 * create time:2022/4/8 15:37
 */
@Configuration
@EnableAuthorizationServer  //开启认证服务器功能
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {

    /**
     * jwt 对称加密密钥
     */
    private String sign_key = "lagou123";


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private LagouAccessTokenConvertor lagouAccessTokenConvertor;

    /**
     * 认证服务器最终是以api接口的方式对外提供服务（校验合法性并生成令牌、校验令牌等）
     * 以api接口方式对外的话，就涉及到接口的访问权限，我们需要在这里进行必要的配置
     *
     * @Param: security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        //相当于打开endpoint 访问接口的开关，这样的话后期我们能够访问该接口
        security
                //允许客户端表单认证
                .allowFormAuthenticationForClients()
                //开启端口/oauth/token_key的访问权限（允许）
                .tokenKeyAccess("permitAll()")
                //开启端口/oauth/check_token的访问权限（允许）
                .checkTokenAccess("permitAll()");
    }

    /**
     * 客户端详情配置,
     * 比如client_id,secret
     * 当前这个服务就如同QQ平台，拉勾网作为客户端需要QQ平台进行登录授权认证等，提前需要到QQ平台注册
     * QQ平台会给拉勾网颁发client_id等必要参数，表明客户端是谁
     *
     * @Param: clients
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);

        //从内存中加载客户端详情

        clients.inMemory()  //客户端信息存储在什么地方，可以在内存中，可以在数据库里
                .withClient("client_lagou")  //添加一个client配置，指定client_id
                .secret("abcxyz")   //指定客户端的密码/安全帽
                .resourceIds("autodeliver")//指定客户端能访问资源id清单，此处的资源id是需要在具体的资源服务器上也配置一样
                //认证类型/令牌颁发面试，可以配置多个在这里，但不一定都用，具体使用哪种方式颁发token，需要客户端调用的时候传递参数指定
                .authorizedGrantTypes("password", "refresh_token","authorization_code")
                //客户端的权限范围
                .scopes("all")
        ;//数据初始化到内存中

        //从数据库中加载客户端详情

        clients.withClientDetails(createJdbcClientDetailsService());
    }

    @Bean
    public JdbcClientDetailsService createJdbcClientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService= new JdbcClientDetailsService(dataSource);
        return  jdbcClientDetailsService;
    }

    /**
     * 配置token令牌管理相关
     *
     * @Param: endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        endpoints
                .tokenStore(tokenStore())   //指定token存储方法
                .tokenServices(authorizationServerTokenServices())  //token服务的一个描述
                .authenticationManager(authenticationManager) //指定认证管理器，随后注入一个到当前类使用即可
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    /*
        该方法用于创建tokenstore对象(令牌存储对象)
        token以什么形式存储
     */
    public TokenStore tokenStore() {
//        return new InMemoryTokenStore();

        //使用 jwt令牌
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 返回jwt令牌转换器（帮助我们生成jwt令牌的）
     * 在这里，我们可以把签名密钥传递进去给转换器对象
     *
     * @Param:
     */
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey(sign_key);//签名密钥
        jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); //验证时使用的密钥，和签名密钥保持一致
        jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
        return jwtAccessTokenConverter;/**/
    }

    /*
        该方法用于创建tokenStore对象（令牌存储对象）
     */
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        //使用默认实现
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setSupportRefreshToken(true);  //是否开启令牌刷新
        defaultTokenServices.setTokenStore(tokenStore());

        //针对令牌的添加
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());

        //设置令牌有效时间（一般为2个小时）
        defaultTokenServices.setAccessTokenValiditySeconds(1200);
        //设置刷新令牌的有效期
        defaultTokenServices.setRefreshTokenValiditySeconds(259200);//3天

        return defaultTokenServices;
    }

}
